#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <net/route.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include "dnsmasq.h"
#include "vpn_pass.h"

typedef struct route_item_t {
  uint32_t ip;								/* host byte order */
  uint32_t mask;							/* host byte order */
} route_item_t;

typedef struct route_item_root_t {
  route_item_t *items;							/* asc order */
  int item_num;
  int size;
} route_item_root_t;

static route_item_root_t *item_roots[256];
static int is_inited = 0;
static struct in_addr vpn_gateway = { 0u };

static void __vpn_add_route(uint32_t dst, uint32_t genmask, uint32_t gw);	/* network byte order */
static void __vpn_print_roots();
static void __vpn_clean();

typedef struct dns_item_t {						/* order: 1) bucket_id; 2) level; 3) hash_id */
  uint32_t hash_id; 							/* xx.xx.A.B, calcuate A.B only */
  struct dns_item_t *next;
  size_t name_len;
  int8_t is_dynamic;							/* dynamic loaded */
  char name[1];
} dns_item_t;

typedef struct dns_item_root_t
{
  dns_item_t head;
  int length;
} dns_item_root_t;

static dns_item_root_t *dns_item_roots[128];
static int is_dns_inited = 0;

static void __vpn_dns_append_item(uint32_t hash_id, char *name, int is_dynamic);
static uint32_t __get_hash_id(char *cp);
static char *__init_stack2(char *cp[2], char *initp);
static void __push_stack2(char *cp[2], char *p);
static uint32_t __hash_string (const char *s);				/* start hash code is 0 */
static char *__str_trim(char *_cp);
static void __vpn_dns_print_roots(FILE *fd);
static void __vpn_dns_print_patch();

int
vpn_init()
{
  if (!is_inited)
    {
      bzero(&item_roots, sizeof(item_roots));
      is_inited = 1;
      my_syslog(LOG_INFO, "......[vpn_init] succ");
    }
  return 0;
}

void
vpn_check_command(char *hostname)			
{
  char *cp, *cp2, *cp_last;

  if (strncmp(hostname, "cmd-", 4))
    return;
  my_syslog(LOG_INFO, "......check-cmd %s", hostname);
  vpn_init();
  vpn_dns_init();

  cp = hostname + 4;
  if (!strncmp(cp, "set-gateway-", 12))				/* fmt: cmd-set-gateway-xx.xx.xx.xx.fakedns-vpn.taobao.com */
    {
      cp += 12;
      if ((cp_last = strstr(cp, ".fakedns-vpn.taobao.com")) && cp_last > cp)
	{
	  struct in_addr ip;
	  *cp_last = '\0';
	  if (inet_pton(AF_INET, cp, &ip) == 1)
	    {
	      /* if gateway is modified, clean the cached tables */
	      vpn_gateway.s_addr = ip.s_addr;
	      __vpn_clean();
	      my_syslog(LOG_INFO, "......[cmd-set-gateway] %s is succ", cp);
	    }
	  *cp_last = '.';					/* restore old value */
	}
      return;
    }
  if (!strncmp(cp, "add-domain-", 11))				/* fmt: cmd-add-domain-specific_domain.fakedns-vpn.taobao.com */
    {
      cp += 11;
      if ((cp_last = strstr(cp, ".fakedns-vpn.taobao.com")) && cp_last > cp)
	{
	  *cp_last = '\0';
	  if (!vpn_dns_check_hostname(cp))
	    __vpn_dns_append_item( __get_hash_id(cp), cp, 1 );
	  // my_syslog(LOG_INFO, "......[cmd-add-domain] %s is succ", cp);
	  *cp_last = '.';					/* restore to old value */
	}
      return;
    }
  if (!strncmp(cp, "add-route-", 10))				/* fmt: cmd-add-route-ipaddr-netmask.fakedns-vpn.taobao.com */
    {
      cp += 10;
      if ((cp_last = strstr(cp, ".fakedns-vpn.taobao.com")) && cp_last > cp)
	{
	  *cp_last = '\0';
	  if ((cp2 = strchr(cp, '-')))
	    {
	      struct in_addr n_ip, n_mask;
	      *cp2 = '\0';
	      if (inet_pton(AF_INET, cp, &n_ip) == 1 && n_ip.s_addr != 0u 
		  && inet_pton(AF_INET, cp2+1, &n_mask) == 1 && n_mask.s_addr != 0u)
		{
		  vpn_check_ip("fake-hostname", n_ip.s_addr, n_mask.s_addr);
		}
	      *cp2 = '-';					/* restore */
	    }
	  *cp_last = '.';					/* restore to old value */
	}
      return;
    }
  if (!strncmp(cp, "print-route", 11))				/* print cached route table */
    {
      cp += 11;							/* enable print-route-00 print-route-01 etc */
      if ((cp_last = strstr(cp, ".fakedns-vpn.taobao.com")) && cp_last >= cp)
	__vpn_print_roots(NULL);
      return;
    }
  if (!strncmp(cp, "export-domain-patches", 21))
    {
      cp += 21;							/* enable export-domain-patches-01 etc */
      if ((cp_last = strstr(cp, ".fakedns-vpn.taobao.com")) && cp_last >= cp)
	__vpn_dns_print_patch();
      return;
    }
}

void
vpn_check_ip(char *hostname, uint32_t ip, uint32_t mask)	/* network byte order */
{
  uint32_t root_id;
  route_item_root_t *root;
  route_item_t *item, *item_last;

  if (!vpn_gateway.s_addr)
    return;

  ip = ntohl(ip);					/* convert to host byte order */
  mask = ntohl(mask);
  ip &= mask;

  root_id = ip >> 24;					/* id.xx.xx.xx */
  if (!item_roots[root_id])
    {
      root = item_roots[root_id] = (route_item_root_t *)calloc(1, sizeof(route_item_root_t)); 
      root->size = 4;
      root->items = (route_item_t *)calloc(4, sizeof(route_item_t)); 
    }
  else
    root = item_roots[root_id];
  if (root->size == root->item_num)
    {
      /* need to enlarge the item table */
      int new_size = (root->size < 32) ? root->size*2 : root->size+16;
      route_item_t *new_items = realloc(root->items, sizeof(route_item_t)*new_size);
      bzero(new_items + root->size, (new_size - root->size)*sizeof(route_item_t));
      root->items = new_items;
      root->size = new_size;
    }
  for (item = root->items, item_last = item + root->item_num; item < item_last; item++)
    {
      if (item->ip == (ip & item->mask))			/* case(1): ip/mask was covered already, skip */
	{
	  printf("the %08x %08x was duplicated\n", ip, mask);
	  return;								
	}
      if (ip < item->ip)					/* case(2): insert before item */
	{
	  for (; item_last > item; item_last--)
	      item_last[0] = item_last[-1];
	  item_last = NULL;					/* rewind to header */
	  break;
	}
    }
  if (item == item_last)					/* case(3): insert to the end */
    {
      item->ip = ip;
      item->mask = mask;
    }
  item->ip = ip;
  item->mask = mask;
  root->item_num++;

  __vpn_add_route(htonl(ip), htonl(mask), vpn_gateway.s_addr);

  if (1)
    {
      struct in_addr n_ip, n_mask;
      char ip_buf[32], mask_buf[32], gw_buf[32];

      n_ip.s_addr = htonl(ip);
      n_mask.s_addr = htonl(mask);
      my_syslog(LOG_INFO, "......[vpn_check_ip_add_route] %s as %s mask %s gw %s", hostname, 
		inet_ntop(AF_INET, &n_ip, ip_buf, sizeof(ip_buf)), 
		inet_ntop(AF_INET, &n_mask, mask_buf, sizeof(mask_buf)),
		inet_ntop(AF_INET, &vpn_gateway, gw_buf, sizeof(gw_buf)));
    }
}

void 
__vpn_add_route(uint32_t dst, uint32_t genmask, uint32_t gw)	/* network byte order */
{
  struct rtentry route;
  int sockfd;
  struct sockaddr_in *rt_dst, *rt_gateway, *rt_genmask;
  int rc;

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd == -1)
    {
      perror("create socket failed\n");
      return;
    }

  bzero(&route, sizeof(route));
  rt_dst = (struct sockaddr_in *) &route.rt_dst;
  rt_gateway = (struct sockaddr_in *) &route.rt_gateway;
  rt_genmask = (struct sockaddr_in *) &route.rt_genmask;

  rt_dst->sin_family = AF_INET;
  rt_dst->sin_addr.s_addr = dst;
  rt_genmask->sin_family = AF_INET;
  rt_genmask->sin_addr.s_addr = genmask;
  rt_gateway->sin_family = AF_INET;
  rt_gateway->sin_addr.s_addr = gw;

  route.rt_flags = RTF_UP | RTF_GATEWAY;
  if (genmask == MASK_32)
    route.rt_flags |= RTF_HOST;
  if ((rc = ioctl(sockfd, SIOCADDRT, &route)) != 0) 
    {
      perror("SIOCADDRT failed");
      return;
    }
}

void 
__vpn_clean()
{
  route_item_root_t *root;
  unsigned int i;

  for (i=0; i < sizeof(item_roots)/sizeof(route_item_root_t *); i++)
    {
      root = item_roots[i];
      if (!root)
	continue;
      free(root->items);
      free(root);
      item_roots[i] = NULL;
    }
}

void
__vpn_print_roots()
{
  route_item_root_t *root;
  route_item_t *item, *item_last;
  char ip_buf[32], mask_buf[32], gw_buf[32];
  unsigned int i, counter;

  my_syslog(LOG_INFO, "......[route-table-%03d] %-16s  %-16s  %-16s", 0, "Destination", "Gateway", "Genmask");
  for (i=counter=0; i < sizeof(item_roots)/sizeof(route_item_root_t *); i++)
    {
      root = item_roots[i];
      if (!root || !root->item_num)
	continue;
      for (item = root->items, item_last = item + root->item_num; item < item_last; item++)
	{
	  uint32_t n_ip = htonl(item->ip);
	  uint32_t n_mask = htonl(item->mask);
	  my_syslog(LOG_INFO, "......[route-table-%03d] %-16s  %-16s  %-16s", i, 
		    inet_ntop(AF_INET, &n_ip, ip_buf, sizeof(ip_buf)), 
		    inet_ntop(AF_INET, &vpn_gateway, gw_buf, sizeof(gw_buf)), 
		    inet_ntop(AF_INET, &n_mask, mask_buf, sizeof(mask_buf)));
	  counter++;
	}
    } 
  my_syslog(LOG_INFO, "......total %d", counter);
}

int 
vpn_dns_init()
{
#define GFWLIST_CONF	"/opt/etc/dnsmasq-gfw.list"
#define HOTFIX_CONF	"/opt/etc/dnsmasq-hotfix.list"
  FILE *fd;
  char *fnames[] = { GFWLIST_CONF, HOTFIX_CONF, NULL };
  char line[LINE_MAX], *name;
  uint32_t hash_id, i;

  if (is_dns_inited)
    return 0;

  bzero(&dns_item_roots, sizeof(dns_item_roots));
  for (i=0; fnames[i] != NULL; i++)
    {
      if (!(fd = fopen(fnames[i], "r")))
	continue;
      while (fgets(line, LINE_MAX, fd))
	{
	  /* read line & trim */
	  name = __str_trim(line);
	  if (*name == '\0' || *name == '#')
	    continue;
	  hash_id = __get_hash_id(name);
	  __vpn_dns_append_item(hash_id, name, 0);
	}
      fclose(fd);
      my_syslog(LOG_INFO, "......[vpn_dns_init] %s succ", fnames[i]);
    }
  is_dns_inited = 1;
  return 1;
}

void 
__vpn_dns_append_item(uint32_t hash_id, char *name, int is_dynamic)
{
  uint32_t root_id;
  dns_item_root_t *root;
  dns_item_t *item, *item_last;
  size_t name_len;

  root_id = hash_id % (sizeof(dns_item_roots)/sizeof(dns_item_root_t *));
  root = dns_item_roots[root_id];
  if (!root)
      root = dns_item_roots[root_id] = (dns_item_root_t *)calloc(1, sizeof(dns_item_root_t)); 

  for (item_last = &root->head, item = root->head.next; item != NULL; item = item->next)
    item_last = item;
  name_len = strlen(name);
  item = calloc(1, sizeof(dns_item_t) + name_len);

  item->hash_id = hash_id;
  strcpy(item->name, name);
  item->name_len = name_len;
  item->is_dynamic = is_dynamic;
  item_last->next = item;
  item->next = NULL;
  root->length++;

  if (is_dynamic)
    my_syslog(LOG_INFO, "......[add-domain-%03d] %s hash_id=%x", root_id, name, hash_id);
}

int 
vpn_dns_check_hostname(char *hostname)
{
  uint32_t root_id;
  dns_item_root_t *root;
  dns_item_t *item;
  uint32_t hash_id;
  int counter;
  size_t hostname_len;

  printf("lookup hostname=%s\n", hostname);
  hash_id = __get_hash_id(hostname);
  root_id = hash_id % (sizeof(dns_item_roots)/sizeof(dns_item_root_t *));
  root = dns_item_roots[root_id];
  if (!root)
    return 0;
  hostname_len = strlen(hostname);
  for (item = root->head.next, counter = 0; item != NULL; item = item->next, counter++)
    if (hash_id == item->hash_id && hostname_len >= item->name_len)
      {
	if (strcmp(item->name, hostname + hostname_len - item->name_len) != 0)
	  continue;
	printf("lookup hostname=%s, counter=%d, bucket_size=%d\n", hostname, counter, root->length);
	return 1;
      }
  return 0;
}

uint32_t 
__get_hash_id(char *name)
{ 
  char *dot_stack[2], *cp;
  uint32_t hash_id;

  /* fetch string need to be hashed */
  for (cp = __init_stack2(dot_stack, name); *cp != '\0'; cp++)
    if (*cp == '.')
      __push_stack2(dot_stack, cp+1);
  hash_id = __hash_string(dot_stack[0]);
  return hash_id;
}

char *
__init_stack2(char *cp[2], char *p)
{
  cp[0] = cp[1] = p;
  return p;
}

void 
__push_stack2(char *cp[2], char *p)
{
  cp[0] = cp[1];
  cp[1] = p;
}

uint32_t   /* hash function from mutt-0.95 */
__hash_string (const char *_s)						/* start hash code is 0 */
{
    uint32_t h = 14971u;
    const unsigned char *s = (const unsigned char *)_s;

    while (*s)
      h += (h << 7) + *s++;
    return h;
}

char *
__str_trim(char *_cp)
{
  char *cp, *cpaux;

  if (*_cp == '\0') return(_cp);
  cp = _cp;
  while (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n')
    cp++;
  cpaux = cp + strlen(cp) - 1;
  if (cpaux >= cp)
    while (*cpaux == ' ' || *cpaux == '\t' || *cpaux ==
	   '\r' || *cpaux == '\n') *cpaux-- = '\0';
  if (cp != _cp)
    {
      /* copy back */
      for (cpaux=_cp; *cp; )
	*cpaux++ = *cp++;
      *cpaux = '\0';  
    }
  return(_cp);
}

void 
__vpn_dns_print_roots(FILE *fd)
{
  unsigned int i;
  for (i=0; i < sizeof(dns_item_roots)/sizeof(dns_item_root_t *); i++)
    {
      dns_item_root_t *root = dns_item_roots[i]; 
      fprintf(fd, "[%d] length = %d\n", i, root->length);
    }
}


void 
__vpn_dns_print_patch()
{
  unsigned int i, counter;
  dns_item_root_t *root; 
  dns_item_t *item;

  my_syslog(LOG_INFO, "......[domain-patches-%03d] %s", 0, "....");
  for (i=counter=0; i < sizeof(dns_item_roots)/sizeof(dns_item_root_t *); i++)
    {
      if (!(root = dns_item_roots[i]))
	continue;
      for (item = root->head.next; item != NULL; item = item->next)
	if (item->is_dynamic)
	  {
	    my_syslog(LOG_INFO, "......[domain-patches-%03d] %s", i, item->name);
	    counter++;
	  }
    }
  my_syslog(LOG_INFO, "......total %d", counter);
}

/*
int main(int arc, char **argv)
{
  vpn_dns_init();
  vpn_dns_check_hostname("leeyg.appspot.com");
  vpn_dns_check_hostname("appspot.com");
  vpn_dns_check_hostname("leeyg.webcache.googleusercontent.com");
  vpn_dns_check_hostname("webcache.googleusercontent.com");
  vpn_dns_check_hostname("tuite.googlecode.com");
  vpn_dns_check_hostname("leeygappspot.com");
  vpn_dns_check_hostname("java.sun.com");
  printf("%d\n", vpn_dns_check_hostname("xxx.yahoo.com"));

}
*/

/*
static char *buf[] = 
{
  "69.171.228.0", "255.255.255.0",
  //"192.168.1.1", "255.255.255.255",
  //  "192.168.10.5", "255.255.255.255",
  // "192.168.10.2", "255.255.255.255",
  //  "192.168.10.3", "255.255.255.255",
  // "192.168.7.0", "255.255.255.0",
  // "192.168.7.5", "255.255.255.255",
  //"10.8.0.1", "255.255.0.0",
  NULL
};

int main(int arc, char **argv)
{
  int i;
  uint32_t ip, mask;
  vpn_init();
  for (i=0; buf[i] != NULL; i+=2)
    {
      inet_pton(AF_INET, buf[i], &ip);
      inet_pton(AF_INET, buf[i+1], &mask);
      vpn_check_ip(ip, mask);
    }
  vpn_print_roots(stdout);
  printf("%u\n", ~((1u<<(32-24)) - 1));
}
*/
